/*
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is ST_EXT-S_UIMAST.
 *
 * The Initial Developer of the Original Code is
 * University of Rome, Tor Vergata
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 */

package it.uniroma2.art.uimast.impl;

import it.uniroma2.art.ontapi.ARTResource;
import org.apache.uima.cas.*;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * Initializes a feature of a feature structure.
 * @author <a href="mailto:manuel.fiorelli@gmail.com">Manuel Fiorelli</a>
 */
abstract class FeatureProjection extends CommonDOMFacilities
{
	@Override
    public int hashCode()
    {
        return feature.getName().hashCode();
    }

	@Override
    public boolean equals(Object o)
    {
        if(o == null)
            return false;
        
        if(!getClass().equals(o.getClass()))
        {
            return false;
        } else
        {
            FeatureProjection other = (FeatureProjection)o;
            return feature.equals(other.feature);
        }
    }
	
	/**
	 * Constructs a <code>FeatureProjection</code> object.
	 * @param classProjection the owner class projection
	 * @param feature the feature to initialize
	 * @param dataSource the data source to use
	 */
    public FeatureProjection(ClassProjection classProjection, Feature feature, DataSource dataSource)
    {
        this.classProjection = classProjection;
        this.feature = feature;
        this.dataSource = dataSource;
    }


    /**
     * Initializes a feature of the feature structure generated during the projection of the ordered pair <code>&lt;domainObject, annot&gt;</code>
     * @param domainObject the domain object
     * @param annot the annotation (associated to <code>domainObject</code>)
     * @param fs the feature structure
     */
    public abstract void apply(ARTResource domainObject, ARTResource annot, FeatureStructure fs);

    /**
     * Factory method.
     * @param classProjection the owner class projection
     * @param e the DOM element describing the projection
     * @return a feature projection
     * @throws Exception
     */
    public static FeatureProjection create(ClassProjection classProjection, Element e)
        throws Exception
    {
    	//// Gets the feature name.
        if(!hasAttributeNS(e, AnnotationProjection.PROJ_NS, "name"))
            throw new Exception("Missing feature name");
        String featureName = getAttributeNS(e, AnnotationProjection.PROJ_NS, "name");
        
        // Gets the type system.
        TypeSystem typeSystem = classProjection.getCAS().getTypeSystem();
        
        // Gets the destination type
        Type destType = classProjection.getDestinationType();
        
        // Gets the feature
        Feature feature = destType.getFeatureByBaseName(featureName);
        if(feature == null)	// checks that the feature exists.
            throw new Exception("The feature \"" + destType.getName() + ":" + featureName + "\" doesn't exist");
       
        // Gets the feature range.
        Type featureRange = feature.getRange();
        
        // If the feature is single-valued.
        if(featureRange.isPrimitive() || typeSystem.getParent(featureRange).getName().equals("uima.cas.String"))
            return new SingleValuedFeature(classProjection, feature, e);
        if(featureRange.isArray())	// if it is multi-valued.
            return new ArrayValuedFeature(classProjection, feature, e);
        else	// otherwise, throws an exception.
            throw new Exception("The type \"" + featureName + "\" isn't valid");
    }

    /**
     * Factory method.
     * @param classProjection the owner class projection
     * @param shortName the feature short name
     * @param mode the projection mode: either <code>value</code> or <code>property</code>
     * @param v the value lexical representation or the property URI
     * @return a feature projection
     * @throws Exception
     */
    public static FeatureProjection create(ClassProjection classProjection, String shortName, String mode, String v)
        throws Exception
    {
    	// Gets the type system.
        TypeSystem typeSystem = classProjection.getCAS().getTypeSystem();
        
        // Gets the destination type
        Type destType = classProjection.getDestinationType();
        
        // Gets the feature.
        Feature feature = destType.getFeatureByBaseName(shortName);
        if(feature == null)	// checks that the feature exists
            throw new Exception("The feature \"" + destType.getName() + ":" + shortName + "\" doesn't exist");
        
        // Gets the feature range.
        Type featureRange = feature.getRange();
        
        // If the feature is single-valued.
        if(featureRange.isPrimitive() || typeSystem.getParent(featureRange).getName().equals("uima.cas.String"))
            return new SingleValuedFeature(classProjection, feature, mode, v);
        if(featureRange.isArray()) // if it is multi-valued.
            return new ArrayValuedFeature(classProjection, feature, mode, v);
        else	// otherwise, throws an exception.
            throw new Exception("The type \"" + destType + "\" isn't valid");
    }

    /**
     * Gets the managed feature.
     * @return a feature
     */
    public Feature getFeature()
    {
        return feature;
    }
    

    /**
     * Serializes this feature projection.
     * @param e A DOM element to augment
     */
    void toDOM(Element e)
    {
        Document doc = e.getOwnerDocument();
        Element f = doc.createElement("feature");
        e.appendChild(f);
        f.setAttribute("name", feature.getShortName());
        dataSource.toDOM(f);
    }

    /** The owner class projection. */
    protected ClassProjection classProjection;
    
    /** The feature to initializes. */
    protected Feature feature;
    
    /** The data source to use. */
    protected DataSource dataSource;
}
